package com.ng.common.redis.lock.aspect;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import com.ng.common.cache.annotation.TrCache;
import com.ng.common.cache.aspect.CacheStandardEvaluationContext;
import com.ng.common.exception.NgException;
import com.ng.common.redis.RedisUtils;
import com.ng.common.redis.lock.annotation.RedisLock; 

/**
 * 锁的切面编程<br/>
 * 针对添加@RedisLock 注解的方法进行加锁
 *

 *
 * @author partner4java
 *
 */
@Component
@Aspect
public class LockAspect {
    //是否开启redis缓存  true开启   false关闭
    @Value("${spring.redis.open: false}")
    private boolean open = false;

    private static final String DEFAULT_PREFIX = "_tr_lock_";
    
    @Autowired
    private RedisUtils redisUtil;

    private Logger logger = LoggerFactory.getLogger(getClass());

  
    
    @Around("@annotation(redisLock)") 
    public Object doAround(ProceedingJoinPoint joinPoint , RedisLock redisLock) throws Throwable {
        if(!open){
            logger.warn("未开启redis,reids同步锁失效！");
            Object  res = joinPoint.proceed();
            return res;
        }

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        // 获得当前访问的class
        Class<?> className = joinPoint.getTarget().getClass();
        // 获得访问的方法名
        String methodName = joinPoint.getSignature().getName();
        // 得到方法的参数的类型
        Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        Object[] args = joinPoint.getArgs();
        String key = "";
        int expireTime = 30;//秒
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);
            method.setAccessible(true);
            // 判断是否存在@RedisLock注解
            if (method.isAnnotationPresent(RedisLock.class)) {
                 
                 key =  redisLock.key();
                boolean classPrefix =  redisLock.classNamePrefixKey();

              //请求的参数
        		String[] paramNames = signature.getParameterNames();
        		

        		if(StringUtils.isBlank(key)) {
        			// key为空 就拼所有参数得key-value
        			key = className.getName() + method.getName() ;

        		} else if(key.contains("'") || key.contains("#")){
 
        			// 判断key是否有表达式
        			key = (classPrefix ? className.getName() + method.getName() : "" ) + getEpValue(key, method.getName(), joinPoint.getTarget().getClass().getSimpleName(), paramNames, args);

        		}
        		 
        		
        		key = DEFAULT_PREFIX + ":" + key ;

                expireTime = getExpireTime(redisLock);
            }
        } catch (Exception e) {
            throw new RuntimeException("redis分布式锁注解参数异常", e);
        }
        Object res = new Object();
        if (redisUtil.lock(key, expireTime, TimeUnit.SECONDS)) {
            try {
                res = joinPoint.proceed();
                return res;
            } catch (Exception e) {
                throw new NgException(e.getMessage());
            } finally {
               redisUtil.unlock(key);
            }
        }else{
            throw new NgException("数据已被锁定，请刷新页面");
        }
    }


    private int getExpireTime(RedisLock annotation) {
        return annotation.expireTime();
    }



	ExpressionParser ep = new SpelExpressionParser();
	CacheStandardEvaluationContext context =new CacheStandardEvaluationContext();


	private synchronized String getEpValue(String script , String method , String className , String[] argNames , Object[] argValues) {

		context.clearVals();

		for(int i = 0 ; i < argNames.length ; i++) {
			context.setVariable(argNames[i], argValues[i]);

			context.setVariable("p" + i, argValues[i]);
		}

		context.setVariable("method", method);
		context.setVariable("className", className);

		Expression e = ep.parseExpression(script);

		Object value = e.getValue(context );

		String key = value == null ? "" : value.toString();

		return key ;

	}



}
